perf(bake): kill the per-tile recompute that dominated Inland-ENC bakes#21
Merged
Conversation
Unmapped S-57 area features (no S-101 class mapping) take the QUESMRK1 '?' fallback in the per-tile emit, which anchors the marker at the area's pole-of-inaccessibility (areaRepresentativePoint). Those features carry a null/errored portrayal stream, so buildLabelCache skipped them and the polylabel search ran LIVE for every tile the polygon spanned — quadratic in the vertex count and repeated across hundreds of tiles. Inland-ENC cells are full of such areas (objl 17000+ IENC classes with no mapping, over huge river/fairway polygons), which made them minutes to bake. Extend buildLabelCache to also precompute the representative point for the QUESMRK1 fallback (and the stream-independent INFORM01 marker), mirroring the emit-side guards exactly so the cached set stays a strict superset of what the per-tile path consults. Output is byte-identical (verified: same sha256 on a German IENC cell before/after); the search now runs once per cell instead of once per tile. 1R8BR858 31.4s->1.9s, 1W7D2250 110s->2.9s.
An area/line feature whose stroked boundary differs from its full geometry (needsDrawableBoundary: MASK/USAG edge flags, or a coast-coincident edge on a non-coast-definer area) restrokes only the drawableLineParts SUBSET. That subset was reconstructed AND reprojected with the transcendental web-mercator projection (tan/log/cos) on EVERY tile the feature spans — while the fill geometry has ridden the tile-invariant geo_world world-coord cache for a while. On Inland-ENC river cells (fairways/depth areas with long shared coast boundaries, DEPCNT contours, masked meta boundaries) this dominated the bake: 1R7EMS02 ran ~1.3 billion transcendental projections, ~41k per tile. Cache the drawn boundary once per cell (DrawnBoundary: the drawableLineParts geometry + each vertex's lonLatToWorld), so the per-tile stroke reprojects with a linear worldToTile. The cache condition mirrors the emit-side guard exactly — needsDrawableBoundary, ANY prim (a masked LINE takes the same path and was the bulk of the cost), not areas only. Byte-identical (project == worldToTile ∘ lonLatToWorld; verified same sha256 on 6 German IENC cells). 1R7EMS02 127s->48s.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Inland-ENC cells were minutes to bake because two per-feature computations ran
live on every tile a feature spans instead of once per cell. Both are moved into
the baker's existing per-cell cache pass. Output is byte-identical — verified
by matching sha256 on German IENC cells before/after; each cache is a strict
superset of what the per-tile path consults, so a cached value is at worst spurious,
never wrong.
1. Cache the drawn boundary's world coords per cell
An area/line whose stroked boundary is the masked subset of its geometry
(
needsDrawableBoundary— MASK/USAG edge flags, or a coast-coincident edge) restrokedonly
drawableLineParts. That subset is tile-invariant, but it was reconstructed andreprojected with the transcendental web-mercator projection (
tan/log/cos) onevery tile — while the fill geometry has ridden the
geo_worldworld-coord cache for awhile. On river cells with long shared coast boundaries this was the last per-tile
projection hotspot:
1R7EMS02ran ~1.3 billion projections (~41k/tile).Now
buildDrawnBoundaryassembles the drawn parts once per cell and precomputes eachvertex's
lonLatToWorld, so the per-tile stroke reprojects with a linearworldToTile.Cache guard mirrors the emit-side guard exactly (any prim, not areas only — a masked
LINE takes the same path and was the bulk of the cost).
1R7EMS02: 127s → 48s.2. Cache the '?' (QUESMRK1) fallback label point
Unmapped S-57 areas (no S-101 class) take the QUESMRK1
?fallback, anchored at thepolygon's pole-of-inaccessibility (
areaRepresentativePoint). They carry a null/erroredportrayal stream, so
buildLabelCacheskipped them and the polylabel search — quadraticin vertex count — ran live for every tile the polygon spanned. IENC cells are full of
these (objl 17000+ classes with no mapping, over huge river/fairway polygons).
buildLabelCachenow also precomputes the representative point for the QUESMRK1 fallback(and the stream-independent INFORM01 marker), so the search runs once per cell.
1R8BR858: 31.4s → 1.9s.1W7D2250: 110s → 2.9s.Notes
project == worldToTile ∘ lonLatToWorld, and a null cache slotkeeps the feature on the live reconstruct-and-project path.
chart.zig(wire the cache in the bake pass),s57.zig(DrawnBoundary+Cell.drawn_boundary),scene.zig(buildDrawnBoundary, extendedbuildLabelCache,cached stroke path).